home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr47 / pctuto.zip / DISK3.EXE / lha / CHAP15-2.DOC < prev    next >
Text File  |  1990-07-19  |  21KB  |  539 lines

  1.  
  2.  
  3.  
  4.              The PC Assembler Tutor                                        148
  5.              ______________________
  6.  
  7.  
  8.              THE STACK
  9.  
  10.              Up to this time we have used the stack for temporary storage. If
  11.              you want to temporarily save either a register or a value in
  12.              memory, you push it:
  13.  
  14.                  push ax
  15.                  push variable1
  16.  
  17.              and if you want to get them back you pop them:
  18.  
  19.                  pop  variable1
  20.                  pop  ax
  21.  
  22.              This is always a word (2 bytes) at a time. When you pop the
  23.              stack, the 8086 gives you back the words in reverse order. Thus
  24.              if you push the following:
  25.  
  26.                  push variable1
  27.                  push variable2
  28.                  push variable3
  29.                  push variable4
  30.                  push variable5
  31.  
  32.              then in order to get the data back in the same place, you need to
  33.              pop in this order:
  34.  
  35.                  pop variable5
  36.                  pop variable4
  37.                  pop variable3
  38.                  pop variable2
  39.                  pop variable1
  40.  
  41.              It pops the last thing that was pushed that hasn't been popped
  42.              yet. 
  43.  
  44.              Nothing has been said about where the stack is or how it
  45.              operates. It's time to change that. When the operating system
  46.              starts a program, it looks for a stack segment. If the stack
  47.              segment has been properly defined, the operating system puts the
  48.              stack segment's segment address in SS (the stack segment
  49.              register) and sets SP (the stack pointer) to point to the first
  50.              byte AFTER the end of the stack segment. Exactly where this is
  51.              depends on how large you have defined your stack segment. SS and
  52.              SP are set, and there is nothing on the stack. 
  53.  
  54.              When you push something:
  55.  
  56.                  push dx
  57.  
  58.              the 8086 subtracts 2 from SP (making one word of space) and puts
  59.              that thing at the new address in SP. SP contains the address of
  60.  
  61.              ______________________
  62.  
  63.              The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
  64.  
  65.  
  66.  
  67.  
  68.              Chapter 15 - Subroutines                                      149
  69.              ________________________
  70.  
  71.              the last thing pushed. 
  72.  
  73.              This means that SP is decreasing, and the stack segment is
  74.              filling up from back to front. In the topsy-turvy world of
  75.              stacks, when you put things on the stack, the stack grows
  76.              downward. What makes things especially confusing is that many
  77.              book writers will picture a stack:
  78.  
  79.                              variable1
  80.                              variable2
  81.                              ax
  82.                              dx
  83.  
  84.              and not bother to tell you whether the stack is growing upwards
  85.              or downwards or where the stack top is. In this book, the stack
  86.              TOP will always be visually on the BOTTOM. High addresses will be
  87.              visually up and low addresses will be visually down. You need to
  88.              get used to SP decreasing as the stack gets larger, and this is
  89.              the easiest way to do it. So, if you have the instructions:
  90.  
  91.                       push ax
  92.                       push variable1
  93.                       push si
  94.                       push di
  95.  
  96.              after these instructions, the stack will look like this:
  97.  
  98.                            VALUE               ADDRESS
  99.  
  100.                            ax                  sp + 6
  101.                            variable1           sp + 4
  102.                            si                  sp + 2
  103.                  sp ->     di                  sp + 0
  104.  
  105.              When you pop a value, the 8086 moves the word (2 bytes) at SP to
  106.              the appropriate location and INCREMENTS SP by 2.
  107.  
  108.                  pop  di
  109.  
  110.              You would now have:
  111.  
  112.                            VALUE               ADDRESS
  113.  
  114.                            ax                  sp + 4
  115.                            variable1           sp + 2
  116.                  sp ->     si                  sp + 0
  117.  
  118.              As long as you are just using PUSH and POP, this is entirely self
  119.              regulating. SS is set, and SP is modified by the 8086 without you
  120.              doing anything. It is now time to get more sophisticated.
  121.  
  122.              In our C example:
  123.  
  124.                  my_procedure (variable1, variable2, variable3) ;
  125.  
  126.              we generated the code:
  127.  
  128.  
  129.  
  130.  
  131.  
  132.              The PC Assembler Tutor                                        150
  133.              ______________________
  134.  
  135.                  push variable3
  136.                  push variable2
  137.                  push variable1
  138.                  call my_procedure
  139.  
  140.              What will the stack look like upon entry to my_procedure? That
  141.              depends on whether my_procedure is a near procedure or a far
  142.              procedure. If it is a near procedure, you will have:
  143.  
  144.  
  145.                            VALUE               ADDRESS
  146.  
  147.                            variable3           sp + 6
  148.                            variable2           sp + 4
  149.                            variable1           sp + 2
  150.                  sp ->     old IP              sp + 0
  151.  
  152.              If it is a far procedure, you will have:
  153.  
  154.                            VALUE               ADDRESS
  155.  
  156.                            variable3           sp + 8
  157.                            variable2           sp + 6
  158.                            variable1           sp + 4
  159.                            old CS              sp + 2
  160.                  sp ->     old IP              sp + 0
  161.  
  162.              Therefore, the variables are in different places relative to SP
  163.              depending on whether it is a near or a far procedure. All
  164.              examples will be with near procedures, but they are all valid for
  165.              far procedures if you adjust for having the old CS on the stack.
  166.  
  167.              How do we access these variables? By using a pointer. We could
  168.              use BX, SI or DI, but they have DS, not SS as their natural
  169.              segment register. The only pointer with SS as the natural segment
  170.              register is BP, the base pointer. Since we are going to use BP,
  171.              we need to push its current value in order to save it:
  172.  
  173.                  push bp
  174.  
  175.              The stack now looks like this:
  176.  
  177.                            VALUE               ADDRESS
  178.  
  179.                            variable3           sp + 8
  180.                            variable2           sp + 6
  181.                            variable1           sp + 4
  182.                            old IP              sp + 2
  183.                  sp ->     old bp              sp + 0
  184.  
  185.              This is the standard way to do it and this is what the stack
  186.              always looks like if you follow the standard method. The standard
  187.              code for setting up the stack for access is:
  188.  
  189.                  push bp
  190.                  mov  bp, sp
  191.  
  192.  
  193.  
  194.  
  195.  
  196.              Chapter 15 - Subroutines                                      151
  197.              ________________________
  198.  
  199.              We give BP the same value as SP, so BP also points to the top of
  200.              the stack and we use BP instead of SP. We now have:
  201.  
  202.                            VALUE               ADDRESS
  203.  
  204.                            variable3           bp + 8
  205.                            variable2           bp + 6
  206.                            variable1           bp + 4
  207.                            old IP              bp + 2
  208.                  bp ->     old bp              bp + 0
  209.  
  210.              Now, if you want to push and pop things, you can do it to your
  211.              heart's content. BP will always point to the set of data that you
  212.              want to work with. Let's take the average of the three variables,
  213.              and print it.
  214.  
  215.                  mov  ax, [bp+4]     ; add the three numbers
  216.                  add  ax, [bp+6]
  217.                  add  ax, [bp+8]
  218.                  mov  dx, 0          ; prepare dx for division
  219.                  mov  bx, 3          ; unsigned divide by 3
  220.                  div  bx
  221.                  call print_unsigned      ; result is in ax
  222.  
  223.              We are using AX, BX, and DX, so we need to push them before doing
  224.              this:
  225.  
  226.                  push ax
  227.                  push bx
  228.                  push dx
  229.  
  230.              After we are done we need to (1) pop the registers and (2)
  231.              restore BP. This is also a pop.
  232.  
  233.                  pop  dx
  234.                  pop  bx
  235.                  pop  ax
  236.                  pop  bp
  237.                  ret
  238.  
  239.              The whole subprogram now looks like this
  240.  
  241.              ;-----
  242.              my_procedure proc near
  243.  
  244.                  push bp             ; set up base pointer
  245.                  mov  bp, sp
  246.                  push ax             ; push registers
  247.                  push bx
  248.                  push dx
  249.                  mov  ax, [bp+4]     ; add the three numbers
  250.                  add  ax, [bp+6]
  251.                  add  ax, [bp+8]
  252.                  mov  dx, 0          ; prepare dx for division
  253.                  mov  bx, 3          ; unsigned divide by 3
  254.                  div bx
  255.                  call print_unsigned      ; result is in ax
  256.  
  257.  
  258.  
  259.  
  260.              The PC Assembler Tutor                                        152
  261.              ______________________
  262.  
  263.                  pop  dx             ; pop registers
  264.                  pop  bx
  265.                  pop  ax
  266.                  pop  bp             ; restore old base pointer
  267.                  ret
  268.  
  269.              my_procedure endp
  270.              ;------
  271.  
  272.              There is only one more improvement to make. If you look at the
  273.              code, it is not clear what [bp+4] refers to. We know where it is,
  274.              but what is it? Therefore, we will always use EQU statements to
  275.              give names to our stack variables. It will be clearer, and if you
  276.              need to change the code, it is much easier to change the EQU
  277.              definition than to change the stack references in the code. As
  278.              usual, we follow the C convention and put EQU names in capital
  279.              letters.
  280.  
  281.              ;-----
  282.              my_procedure proc near
  283.  
  284.                  VAR1 EQU [bp+4]
  285.                  VAR2 EQU [bp+6]
  286.                  VAR3 EQU [bp+8]
  287.  
  288.                  push bp             ; set up base pointer
  289.                  mov  bp, sp
  290.                  push ax             ; push registers
  291.                  push bx
  292.                  push dx
  293.                  mov  ax, VAR1       ; add the three numbers
  294.                  add  ax, VAR2
  295.                  add  ax, VAR3
  296.                  mov  dx, 0          ; prepare dx for division
  297.                  mov  bx, 3          ; divide by 3
  298.                  div bx
  299.                  call print_unsigned      ; result is in ax
  300.                  pop  dx             ; pop registers
  301.                  pop  bx
  302.                  pop  ax
  303.                  pop  bp             ; restore old base pointer
  304.                  ret
  305.  
  306.              my_procedure endp
  307.              ;------
  308.  
  309.              This is a simple example, so it doesn't look that important to
  310.              use the EQU statements. Just wait till you have more complex
  311.              subroutines. By the way, this program does no error checking. (If
  312.              the sum is > 65535 it will give the wrong answer).
  313.  
  314.              There is still one thing to do. When we called the subroutine, we
  315.              pushed the variables on the stack:
  316.  
  317.                  push variable3
  318.                  push variable2
  319.                  push variable1
  320.  
  321.  
  322.  
  323.  
  324.              Chapter 15 - Subroutines                                      153
  325.              ________________________
  326.  
  327.                  call my_procedure
  328.  
  329.              We now want to take them off. Do we need to pop them? No, this is
  330.              trash so they go into the Great Bit Bucket. There are two ways of
  331.              doing this, and this is language dependent.{1} In C, it is the
  332.              STANDARD that the calling routine takes them off, and it is done
  333.              this way:
  334.  
  335.                  push variable3
  336.                  push variable2
  337.                  push variable1
  338.                  call my_procedure
  339.                  add  sp, 6          ; 3 variables = 6 bytes
  340.  
  341.              we simply INCREASE sp by the number of bytes that we pushed on
  342.              the stack. Whoof, they're gone. 
  343.  
  344.              If you use PASCAL or FORTRAN, then the CALLED routine must take
  345.              the variables off the stack on return. How does it do that? There
  346.              is yet another type of return statement:
  347.  
  348.                  ret (6)             ; 3 variables = 6 bytes {2}
  349.  
  350.              causes the 8086 to increase sp by 6 as the last thing it does
  351.              before returning from the subroutine. Which method you use is
  352.              decided by which high-level language you are using.
  353.  
  354.  
  355.                  MACHINE CODE        ASSEMBLER INSTRUCTIONS
  356.  
  357.                                 ;----- 
  358.                                      far_routine proc far 
  359.                     CA 001A               ret (26)     ; hex 1A 
  360.                     CB                    ret 
  361.                                      far_routine endp 
  362.                                 ;----- 
  363.  
  364.                                 ;----- 
  365.                                      near_routine proc near 
  366.                     C2 002C               ret (44)     ; hex 2C 
  367.                     C3                    ret 
  368.                                      near_routine endp 
  369.                                 ;----- 
  370.  
  371.              Here are the four different types of returns along with the
  372.              machine code. Notice that the returns which increment the stack
  373.              have the increment count coded in the machine code. 
  374.  
  375.  
  376.              You may have noticed that even in this first subroutine, pushing
  377.              and popping the registers takes a lot of space. It is fairly
  378.              ____________________
  379.  
  380.              1 And a major reason that is a real pain in keester to have
  381.              multi-language programs.
  382.  
  383.              2 The parentheses are not necessary.
  384.  
  385.  
  386.  
  387.  
  388.              The PC Assembler Tutor                                        154
  389.              ______________________
  390.  
  391.              normal to use 6 registers in a subroutine. This means that you
  392.              will need to write:
  393.  
  394.                  push ax
  395.                  push bx
  396.                  push cx
  397.                  push dx
  398.                  push si
  399.                  push di
  400.  
  401.              at the beginning of the subroutine and:
  402.  
  403.                  pop  di
  404.                  pop  si
  405.                  pop  dx
  406.                  pop  cx
  407.                  pop  bx
  408.                  pop  ax
  409.  
  410.              before returning. This is a lot of space and it gets boring.
  411.              Also, you have to remember to pop in the exact reverse order or
  412.              you will screw things up. Fortunately we have two macros to help
  413.              us. The file  PUSHREGS.MAC has two macros, one called PUSHREGS
  414.              and the other called POPREGS. 
  415.  
  416.              A macro is a set of directions for generating additional
  417.              assembler code before the file is assembled. That's why it is
  418.              called the Microsoft Macro Assembler. You include \pushregs.mac
  419.              at the beginning of the file, and then everytime the assembler
  420.              sees the word PUSHREGS followed by register names it generates
  421.              push instructions. Every time the assembler sees POPREGS followed
  422.              by register names, it generates pop instructions. It generates
  423.              actual text which is assembled later.
  424.  
  425.              The form for generating those push instructions above is:
  426.  
  427.                  PUSHREGS ax, bx, cx, dx, si, di
  428.  
  429.              the word PUSHREGS followed by the registers separated by commas.
  430.              (Make sure there is no comma after the last register). This must
  431.              all be on one line. PUSHREGS pushes the registers in left to
  432.              right order. 
  433.  
  434.              The form for generating those pop instructions above is
  435.  
  436.                  POPREGS ax, bx, cx, dx, si, di
  437.  
  438.              The registers will be popped in the REVERSE order to the way they
  439.              are listed on the line, that is, in RIGHT TO LEFT order.
  440.  
  441.              Notice that the order of registers is the same for both PUSHREGS
  442.              and POPREGS. This is so that you may write the push part:
  443.  
  444.                  PUSHREGS ax, bx, cx, dx, si, di
  445.  
  446.              and then use your word processor to copy the line to the end of
  447.              the subroutine, changing PUSHREGS to POPREGS. This insures that
  448.  
  449.  
  450.  
  451.  
  452.              Chapter 15 - Subroutines                                      155
  453.              ________________________
  454.  
  455.              the pushes and pops will be in exact reverse order. It saves
  456.              space and time, and it generates exactly the same code as if you
  457.              had written all those pushes and pops in the code. Whenever we
  458.              have subroutines in the future, we will always use it.
  459.  
  460.  
  461.              MOVING A STRING
  462.  
  463.              As a final example, we will create a subroutine that moves a
  464.              Pascal string from one place to another. We'll assume that both
  465.              strings are in the current DS, so no segments need to be changed.
  466.  
  467.                  move_string ( from_string, to_string ) ;
  468.  
  469.              where from_string and to_string are the ADDRESSES of the strings.
  470.              The code generated by the Pascal compiler will be:
  471.  
  472.                  mov  ax, offset from_string
  473.                  push ax
  474.                  mov  ax, offset to_string
  475.                  push ax
  476.                  call move_string
  477.                  ; this is Pascal, so the CALLED subroutine must
  478.                  ; get rid of the variables on the stack.
  479.  
  480.              Notice that Pascal pushes this data in left to right order,
  481.              exactly the opposite of how C would handle it. After setting up
  482.              BP, we have:
  483.  
  484.                            from_string offset       bp + 6
  485.                            to_string offset         bp + 4
  486.                            old IP                   bp + 2
  487.                  bp ->     old bp                   bp + 0
  488.  
  489.              Before coding this, you need to know the structure of a Pascal
  490.              text string. The first byte (string[0]) is not text, but the text
  491.              count. The second byte is the first piece of text. This means two
  492.              things. First, the maximum string size in Pascal is 255, the
  493.              largest count that will fit in one byte. Second, you need to move
  494.              'count + 1' bytes. 'count' is how many text bytes there are, but
  495.              then you need to move the count itself. If the string is empty
  496.              (count = 0) you need to move 1 byte, the count byte. Here's the
  497.              code
  498.  
  499.              ; - - - - -
  500.              move_string   proc near
  501.  
  502.                  FROM_ADDRESS  EQU  [bp + 6]
  503.                  TO_ADDRESS    EQU  [bp + 4]
  504.  
  505.                  push bp             ; set up bp
  506.                  mov  bp, sp
  507.                  PUSHREGS  ax, cx, si, di
  508.  
  509.                  mov  si, FROM_ADDRESS    ; source
  510.                  mov  di, TO_ADDRESS      ; destination
  511.                  sub  cx, cx              ; zero cx
  512.  
  513.  
  514.  
  515.  
  516.              The PC Assembler Tutor                                        156
  517.              ______________________
  518.  
  519.                  mov  cl, [si]            ; text byte count of source
  520.                  inc  cl                  ; add 1 for byte count itself
  521.  
  522.              move_loop:
  523.                  mov  al, [si]            ; source to al
  524.                  mov  [di], al            ; al to destination
  525.                  inc  si                  ; move pointers to next byte
  526.                  inc  di
  527.                  loop move_loop
  528.  
  529.                  POPREGS  ax, cx, si, di
  530.                  pop  bp
  531.                  ret (4)                  ; Pascal, so pop offsets.
  532.  
  533.              move_string  endp
  534.              ; - - - - - - -
  535.  
  536.              We still have some more to do, and we'll do it in part three of
  537.              the chapter.
  538.  
  539.